#include <QGuiApplication>
#include <QScreen>
#include <QProcess>
#include <QEventLoop>
#include <QDebug>
#include "X11Picker.h"
#include "X11Monitor.h"
#include <X11/Xlibint.h>
#include <X11/XKBlib.h>
#include <unistd.h>


#define MAXSTR 1000


EventMonitor::EventMonitor(QObject *parent) : QThread(parent)
{
    isPress = false;
}

void EventMonitor::run()
{
    Display* display = XOpenDisplay(0);
    if (display == 0) {
        fprintf(stderr, "unable to open display\n");
        return;
    }

    // Receive from ALL clients, including future clients.
    XRecordClientSpec clients = XRecordAllClients;
    XRecordRange* range = XRecordAllocRange();
    if (range == 0) {
        fprintf(stderr, "unable to allocate XRecordRange\n");
        return;
    }

    // Receive KeyPress, KeyRelease, ButtonPress, ButtonRelease and MotionNotify events.
    memset(range, 0, sizeof(XRecordRange));
    range->device_events.first = KeyPress;
    range->device_events.last  = MotionNotify;

    // And create the XRECORD context.
    XRecordContext context = XRecordCreateContext(display, 0, &clients, 1, &range, 1);
    if (context == 0) {
        fprintf(stderr, "XRecordCreateContext failed\n");
        return;
    }
    XFree(range);

    XSync(display, True);

    Display* display_datalink = XOpenDisplay(0);
    if (display_datalink == 0) {
        fprintf(stderr, "unable to open second display\n");
        return;
    }

    if (!XRecordEnableContext(display_datalink, context,  callback, (XPointer) this)) {
        fprintf(stderr, "XRecordEnableContext() failed\n");
        return;
    }
}

void EventMonitor::callback(XPointer ptr, XRecordInterceptData* data)
{
    ((EventMonitor *) ptr)->handleRecordEvent(data);
}

void EventMonitor::handleRecordEvent(XRecordInterceptData* data)
{
    if (data->category == XRecordFromServer) {
        xEvent * event = (xEvent *)data->data;
        switch (event->u.u.type) {
        case ButtonPress:
            if (filterWheelEvent(event->u.u.detail)) {
                isPress = true;
                mousePressPosition = QPoint(event->u.keyButtonPointer.rootX, event->u.keyButtonPointer.rootY);
                emit buttonPress(
                    event->u.u.detail,
                    event->u.keyButtonPointer.rootX,
                    event->u.keyButtonPointer.rootY);
            }
            else {
                emit mouseWheel(
                    event->u.keyButtonPointer.rootX,
                    event->u.keyButtonPointer.rootY);
            }

            break;
        case MotionNotify:
            if (isPress) {
                emit buttonDrag(
                    event->u.keyButtonPointer.rootX,
                    event->u.keyButtonPointer.rootY);
            }

            break;
        case ButtonRelease:
            if (filterWheelEvent(event->u.u.detail)) {
                isPress = false;
                mouseReleasedPosition = QPoint(event->u.keyButtonPointer.rootX, event->u.keyButtonPointer.rootY);
                emit buttonRelease(
                    event->u.u.detail,
                    event->u.keyButtonPointer.rootX,
                    event->u.keyButtonPointer.rootY);
            }

            break;
        case KeyPress:
            emit keyPress(((unsigned char*) data->data)[1]);

            break;
        case KeyRelease:
            emit keyRelease(((unsigned char*) data->data)[1]);

            break;
        default:
            break;
        }
    }

    fflush(stdout);
    XRecordFreeData(data);
}

bool EventMonitor::filterWheelEvent(int detail)
{
    return detail != WheelUp && detail != WheelDown && detail != WheelLeft && detail != WheelRight;
}


Xdotool::Xdotool()
{
    this->display = XOpenDisplay(nullptr);
    if (this->display == nullptr)
    {
        fprintf(stderr, "xdotool: fail to open display\n");
    }
    this->screen = XDefaultScreen(display);
    this->root_window = RootWindow(display, screen);
    screenWidth = QGuiApplication::primaryScreen()->availableSize().width();
    screenHeight = QGuiApplication::primaryScreen()->availableSize().height();
    initKeyMap();

    connect(&eventMonitor, &EventMonitor::buttonPress, this, [this](int button, int x, int y){
        QPoint p(x, y);
        emit mouseBtnPressed(p);
        if (button == Button1)
            emit mouseLeftBtnPressed(p);
        else if (button == Button3)
            emit mouseRightBtnPressed(p);
        else if (button == WheelUp || button == WheelDown)
            emit mouseWheel(p);
    });

    connect(&eventMonitor, &EventMonitor::buttonRelease, this, [this](int button, int x, int y){
        QPoint p(x, y);
        emit mouseBtnReleased(p);
        if (button == Button1)
            emit mouseLeftBtnReleased(p);
        else if (button == Button3)
            emit mouseRightBtnReleased(p);
    });

    connect(&eventMonitor, &EventMonitor::mouseWheel, this, [this](int x, int y){
        QPoint p(x, y);
        emit mouseWheel(p);
    });

    connect(&eventMonitor, &EventMonitor::keyPress, this, [this](int code){
        emit keyPressed(code);
        QString keyName(XKeysymToString(XkbKeycodeToKeysym(this->display, code, 0, 0)));
        emit keyPressed(keyName);
    });

    connect(&eventMonitor, &EventMonitor::keyRelease, this, [this](int code){
        emit keyReleased(code);
        QString keyName(XKeysymToString(XkbKeycodeToKeysym(this->display, code, 0, 0)));
        emit keyReleased(keyName);
    });

    Picker::instance();
    connect(this, &Xdotool::mouseLeftBtnPressed, &Picker::instance(), &Picker::onButtonPressed);
    connect(this, &Xdotool::mouseLeftBtnReleased, &Picker::instance(), &Picker::onButtonReleased);
    connect(&Picker::instance(), &Picker::wordsPicked, this, &Xdotool::contentPicked);

    connect(qApp, &QCoreApplication::aboutToQuit, this, [this]{
        this->eventMonitor.terminate();
        this->eventMonitor.wait();
    });
    eventMonitor.start();

}

void Xdotool::initKeyMap()
{
    for (unsigned char i = 0; i < 100; i++)
    {
        QString keyName(XKeysymToString(XkbKeycodeToKeysym(this->display, i, 0, 0)));
        if (!keyName.isEmpty())
        {
            keyMap[keyName] = i;
        }
    }
    keyMap["Control"] = keyMap["Control_L"];
    keyMap["ctrl"] = keyMap["Control_L"];
    keyMap["alt"] = keyMap["Alt_L"];
    keyMap["super"] = keyMap["Super_L"];
    keyMap["shift"] = keyMap["Shift_L"];

}

Xdotool &Xdotool::instance()
{
    static Xdotool xdotool;
    return xdotool;
}

Xdotool::~Xdotool()
{
    XCloseDisplay(display);
}

int Xdotool::getMousePosition(int &root_x, int &root_y)
{
    unsigned int mask;

    /*
    Bool XQueryPointer(display, w, root_return, child_return, root_x_return, root_y_return,
                         win_x_return, win_y_return, mask_return)
          Display *display;
          Window w;
          Window *root_return, *child_return;
          int *root_x_return, *root_y_return;
          int *win_x_return, *win_y_return;
          unsigned int *mask_return;
    */
    Window return_window;
    XQueryPointer(display, root_window, &return_window, &return_window, &root_x, &root_y, &root_x, &root_y, &mask);
    // XCloseDisplay(display);
    return 0;
}

void Xdotool::check_status(int status, Window window)
{
    if (status == BadWindow)
    {
        printf("window id # 0x%lx does not exists!", window);
    }

    if (status != Success)
    {
        printf("XGetWindowProperty failed!");
    }
}

unsigned char *Xdotool::get_string_property(const char *property_name, Window window)
{
    if (window == 0 || window == BadWindow)
        return nullptr;
    Atom actual_type, filter_atom;
    int actual_format, status;
    unsigned long nitems, bytes_after;

    filter_atom = XInternAtom(display, property_name, True);
    status = XGetWindowProperty(display, window, filter_atom, 0, MAXSTR, False, AnyPropertyType,
                                &actual_type, &actual_format, &nitems, &bytes_after, &prop);
    check_status(status, window);
    return prop;
}

unsigned long Xdotool::get_long_property(const char *property_name, Window window)
{
    if (window == 0 || window == BadWindow)
        return 0;
    unsigned long long_property = 0;
    auto res = get_string_property(property_name, window);
    if (res != nullptr)
        long_property = static_cast<unsigned long>(prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24));
    return long_property;
}

unsigned long Xdotool::getActiveWindowPID()
{
    unsigned long window;
    window = get_long_property("_NET_ACTIVE_WINDOW", root_window);
    return get_long_property(("_NET_WM_PID"), window);
}

QString Xdotool::getActiveWindowName()
{
    unsigned long window;
    window = get_long_property("_NET_ACTIVE_WINDOW", root_window);
    auto res = get_string_property("_NET_WM_NAME", window);
    QString name;
    if (res != nullptr)
        name = QString(reinterpret_cast<char *>(prop));
    return name;
}

QString Xdotool::getProcessPathByPID(unsigned long pid)
{
    char path[30];
    sprintf(path, "/proc/%ld/exe", pid);
    char buff[200];
    ssize_t buff_len;
    if ((buff_len = readlink(path, buff, 199)) != -1)
    {
        buff[buff_len] = '\0';
        return QString(buff);
    }
    return QString("");
}

QMap<QString, int> Xdotool::getKeyMap()
{
    return keyMap;
}

void Xdotool::screenShot()
{
    QProcess process;
    QEventLoop loop;
    connect(&process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), &loop, &QEventLoop::quit);
    process.start("/bin/bash", QStringList {"/home/jzc/TranslateStar/resources/screenshot.sh"});
    loop.exec();
    if (process.exitCode() == 0)
        emit screenShotDone("/tmp/ocr");

}

void Xdotool::stopMonitor()
{
    eventMonitor.terminate();
    eventMonitor.wait();
}

